#include "telnet_server.h"

#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include <ctype.h>

#include "libtelnet.h"

#include "esp_vfs.h"
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
// 协议说明
// https://www.cnblogs.com/spinsoft/archive/2012/06/27/2566069.html
// https://blog.csdn.net/tus00000/article/details/118613063

#define PORT                        23
#define KEEPALIVE_IDLE              6
#define KEEPALIVE_INTERVAL          5
#define KEEPALIVE_COUNT             3

static const char *TAG = "tcp-server";
static int sockFd=0;

static telnet_t *telnet;
//定义一行命令
static int lineIndex=0;
static char line[200]={0};
extern lua_State*L;

 // TELNET_DO 接收请求
 // TELNET_DONT 拒绝请求
 // TELNET_WONT 发送者希望使某选项
 // TELNET_WILL 发送者想激活某选项，
 // 服务器发送WONT SUPPRESS GO AHEAD和WONT ECHO选项，
 //由于单个字符方式下是假定SUPPRESS GO AHEAD和ECHO同时有效的，禁止这两个选项之一就进入了准行方式。
static const telnet_telopt_t telopts[] = {
    { TELNET_TELOPT_ECHO,      TELNET_WILL, TELNET_DO },
	{ TELNET_TELOPT_SGA,     TELNET_WILL, TELNET_DO },
    { TELNET_TELOPT_TTYPE,     TELNET_WONT, TELNET_DO },
    { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO   },
    { TELNET_TELOPT_ZMP,       TELNET_WONT, TELNET_DO   },
    { TELNET_TELOPT_MSSP,      TELNET_WONT, TELNET_DO   },
    { TELNET_TELOPT_BINARY,    TELNET_WONT, TELNET_DO   },
    { TELNET_TELOPT_NAWS,      TELNET_WONT, TELNET_DONT },
    { -1, 0, 0 }
};


static const char *get_cmd(unsigned char cmd) {
	static char buffer[4];

	switch (cmd) {
	case 255: return "IAC";
	case 254: return "DONT";
	case 253: return "DO";
	case 252: return "WONT";
	case 251: return "WILL";
	case 250: return "SB";
	case 249: return "GA";
	case 248: return "EL";
	case 247: return "EC";
	case 246: return "AYT";
	case 245: return "AO";
	case 244: return "IP";
	case 243: return "BREAK";
	case 242: return "DM";
	case 241: return "NOP";
	case 240: return "SE";
	case 239: return "EOR";
	case 238: return "ABORT";
	case 237: return "SUSP";
	case 236: return "xEOF";
	default:
		snprintf(buffer, sizeof(buffer), "%d", (int)cmd);
		return buffer;
	}
}

static const char *get_opt(unsigned char opt) {
	switch (opt) {
	case 0: return "BINARY";
	case 1: return "ECHO";
	case 2: return "RCP";
	case 3: return "SGA";
	case 4: return "NAMS";
	case 5: return "STATUS";
	case 6: return "TM";
	case 7: return "RCTE";
	case 8: return "NAOL";
	case 9: return "NAOP";
	case 10: return "NAOCRD";
	case 11: return "NAOHTS";
	case 12: return "NAOHTD";
	case 13: return "NAOFFD";
	case 14: return "NAOVTS";
	case 15: return "NAOVTD";
	case 16: return "NAOLFD";
	case 17: return "XASCII";
	case 18: return "LOGOUT";
	case 19: return "BM";
	case 20: return "DET";
	case 21: return "SUPDUP";
	case 22: return "SUPDUPOUTPUT";
	case 23: return "SNDLOC";
	case 24: return "TTYPE";
	case 25: return "EOR";
	case 26: return "TUID";
	case 27: return "OUTMRK";
	case 28: return "TTYLOC";
	case 29: return "3270REGIME";
	case 30: return "X3PAD";
	case 31: return "NAWS";
	case 32: return "TSPEED";
	case 33: return "LFLOW";
	case 34: return "LINEMODE";
	case 35: return "XDISPLOC";
	case 36: return "ENVIRON";
	case 37: return "AUTHENTICATION";
	case 38: return "ENCRYPT";
	case 39: return "NEW-ENVIRON";
	case 70: return "MSSP";
	case 85: return "COMPRESS";
	case 86: return "COMPRESS2";
	case 93: return "ZMP";
	case 255: return "EXOPL";
	default: return "unknown";
	}
}

static void print_encode(const char *buffer, size_t size) {
	const char *in = buffer, *end = buffer + size;
	while (in != end) {
        char temp=*in;
		if (*in == '%') {
			printf("%%%%");
		} else if (isprint(temp)) {
			printf("%c", temp);
		} else {
			printf("%%%02X", *in);
		}
		++in;
	}
}

static void event_print(telnet_t *telnet, telnet_event_t *ev, void *ud) {

    // printf("ev->type: %02X buff:%d  <",ev->type ,ev->data.size);
    // if(ev->data.buffer!=NULL&&ev->data.size>1){
    //     for(int j=0;j< (ev->data.size);j++){
    //         printf(" %02X",ev->data.buffer[j]);
    //     }
    // }
    // printf(">\r\n");

	size_t i;
	switch (ev->type) {
	case TELNET_EV_DATA:
        // 0
        //处理用户输入
		// printf("DATA [%zi] ==> ", ev->data.size);
		// print_encode(ev->data.buffer, ev->data.size);
		// printf("\n");
        //telnet_negotiate(telnet, TELNET_WONT, TELNET_TELOPT_ECHO);
		//telnet_negotiate(telnet, TELNET_WILL, TELNET_TELOPT_ECHO);

		if(ev->data.size==2&&ev->data.buffer[0]==0x0D){
			
			//char tmp[4]={0x1b,0x5d,0x30,0x3b};
			//send(sockFd,tmp,4,0);
			//line[lineIndex]=0;
			//接受控制台输出的字符串
			//printf("line:%s , %d fd:%d\r\n",line,strlen(line),sockFd);
			if(strlen(line)>0){
				//串口数据重定向到socket
				esp_vfs_replace_uart(sockFd);

				printf("\r\n");
				const int ret =luaL_dostring(L,line);
				//恢复串口
				if(ret){
					if (ret == LUA_ERRSYNTAX)
						printf("\e[31;40m Error: %s\r\n", lua_tostring(L, -1));
					else if (ret == LUA_ERRMEM)
						printf("\e[31;40m Error: memory error\r\n");
					else
						printf("\e[31;40m Error: syntax error\r\n");
				}
				//重置串口
				esp_vfs_reset_uart();
				send(sockFd,"\e[31;37mesp32 console:$\e[31;32m",31,0);
			}else{
				send(sockFd,"\r\n\e[31;37mesp32 console:$\e[31;32m",33,0);
			}
			


			memset(line,0,sizeof(line));
			lineIndex=0;
		}else if(ev->data.size==3&&ev->data.buffer[0]==0x1b&&ev->data.buffer[1]==0x5b&&(ev->data.buffer[2]==0x41||ev->data.buffer[2]==0x42)){
			//上下键的键码，忽略
		}else if(ev->data.size==3&&ev->data.buffer[0]==0x1b&&ev->data.buffer[1]==0x5b&&ev->data.buffer[2]==0x44){
			//向左退格按钮
			lineIndex-=1;
			//当没有数据时候
			if(lineIndex<0){
				lineIndex=0;
			}else{
				send(sockFd,ev->data.buffer, ev->data.size,0);
			}

		}else if(ev->data.size==3&&ev->data.buffer[0]==0x1b&&ev->data.buffer[1]==0x5b&&ev->data.buffer[2]==0x43){
			//向右退格按钮
			lineIndex+=1;
			//当没有数据时候
			if(lineIndex>strlen(line)){
				lineIndex=strlen(line);
			}else{
				send(sockFd,ev->data.buffer, ev->data.size,0);
			}

		}else if(ev->data.size==1&&ev->data.buffer[0]==0x08){
			//删除按钮
			char tmp[4]={0x08,0x1b,0x5b,0x4b};
			send(sockFd,tmp, 4,0);
			lineIndex--;
			if(lineIndex<1){
				lineIndex=0;
			}
			line[lineIndex]=0;

		}else{
			//普通字符串
        	send(sockFd,ev->data.buffer, ev->data.size,0);
			//将字符拼接起来
			for(int i=0;i<ev->data.size;i++){
				line[lineIndex+i]=ev->data.buffer[i];
			}
			lineIndex+=ev->data.size;
		}
		
		break;
	case TELNET_EV_SEND:
        // 1
        //处理发送数据
        //printf("TELNET_EV_SEND\r\n");
        send(sockFd,ev->data.buffer, ev->data.size,0);
		break;
	case TELNET_EV_IAC:
		printf("IAC %d (%s)\n", (int)ev->iac.cmd, get_cmd(ev->iac.cmd));
		break;
	case TELNET_EV_WILL:
		printf("WILL %d (%s)\n", (int)ev->neg.telopt, get_opt(ev->neg.telopt));
		break;
	case TELNET_EV_WONT:
		printf("WONT %d (%s)\n", (int)ev->neg.telopt, get_opt(ev->neg.telopt));
		break;
	case TELNET_EV_DO:
        // 5 
		printf("DO %d (%s)\n", (int)ev->neg.telopt, get_opt(ev->neg.telopt));
        if (ev->neg.telopt == TELNET_TELOPT_COMPRESS2){
			telnet_begin_compress2(telnet);
            printf("TELNET_TELOPT_COMPRESS2\r\n");
        }
		break;
	case TELNET_EV_DONT:
		printf("DONT %d (%s)\n", (int)ev->neg.telopt, get_opt(ev->neg.telopt));
		break;
	case TELNET_EV_SUBNEGOTIATION:
		switch (ev->sub.telopt) {
		case TELNET_TELOPT_ENVIRON:
		case TELNET_TELOPT_NEW_ENVIRON:
		case TELNET_TELOPT_TTYPE:
		case TELNET_TELOPT_ZMP:
		case TELNET_TELOPT_MSSP:
			/* print nothing */
			break;
		default:
			printf("SUB %d (%s) [%zi]\n", (int)ev->sub.telopt, get_opt(ev->sub.telopt), ev->sub.size);
			break;
		}
		break;
	case TELNET_EV_ZMP:
		printf("ZMP (%s) [%zi]\n", ev->zmp.argv[0], ev->zmp.argc);
		break;
	case TELNET_EV_TTYPE:
		printf("TTYPE %s %s\n", ev->ttype.cmd ? "SEND" : "IS",
				ev->ttype.name ? ev->ttype.name : "");
		break;
	/* ENVIRON/NEW-ENVIRON commands */
	case TELNET_EV_ENVIRON:
		printf("ENVIRON [%zi parts] ==> %s", ev->environ.size, ev->environ.cmd == TELNET_ENVIRON_IS ? "IS" : (ev->environ.cmd == TELNET_ENVIRON_SEND ? "SEND" : "INFO"));
		for (i = 0; i != ev->environ.size; ++i) {
			printf(" %s \"", ev->environ.values[i].type == TELNET_ENVIRON_VAR ? "VAR" : "USERVAR");
			if (ev->environ.values[i].var != 0) {
				print_encode(ev->environ.values[i].var, strlen(ev->environ.values[i].var));
			}
			printf("\"");
			if (ev->environ.cmd != TELNET_ENVIRON_SEND) {
				printf("=\"");
				if (ev->environ.values[i].value != 0) {
					print_encode(ev->environ.values[i].value, strlen(ev->environ.values[i].value));
				}
				printf("\"");
			}
		}
		printf("\n");
		break;
	case TELNET_EV_MSSP:
		printf("MSSP [%zi] ==>", ev->mssp.size);
		for (i = 0; i != ev->mssp.size; ++i) {
			printf(" \"");
			print_encode(ev->mssp.values[i].var, strlen(ev->mssp.values[i].var));
			printf("\"=\"");
			print_encode(ev->mssp.values[i].value, strlen(ev->mssp.values[i].value));
			printf("\"");
		}
		printf("\n");
		break;
	case TELNET_EV_COMPRESS:
		printf("COMPRESSION %s\n", ev->compress.state ? "ON" : "OFF");
		break;
	case TELNET_EV_WARNING:
		printf("WARNING: %s\n", ev->error.msg);
		break;
	case TELNET_EV_ERROR:
		printf("ERROR: %s\n", ev->error.msg);
		break;
	}
}


static void processSocket(int sock){
    ///----------telnet初始化---------------
    sockFd=sock;
    char buffer[200];
    int len;
    /* create telnet parser instance */
    telnet = telnet_init(telopts, event_print, 0,&sock);
    //telnet_negotiate(telnet, TELNET_WILL,TELNET_TELOPT_COMPRESS2);
    telnet_negotiate(telnet, TELNET_WONT,TELNET_TELOPT_COMPRESS2);
    telnet_printf(telnet, "\e[31;37mesp32 console:$\e[31;32m");
    //开启本地回显
    telnet_negotiate(telnet, TELNET_WONT, TELNET_TELOPT_ECHO);
    //关闭本地回显
    //telnet_negotiate(telnet, TELNET_WILL, TELNET_TELOPT_ECHO);
    
    do{
        len = recv(sock, buffer, sizeof(buffer) - 1, 0);
        //printf("--------5-------\r\n");
        if(len>0){
            for(int i=0;i<len;i++){
                //printf("%02X ",buffer[i]);
            }
            //printf("\r\n-[END]-\r\n");
            telnet_recv(telnet, buffer, len);
        }
    }while(len>0);
    telnet_free(telnet);

}

void telnet_server_task(void *pvParameters){

    char addr_str[128];
    int addr_family = (int)pvParameters;
    int ip_protocol = 0;
    int keepAlive = 1;
    int keepIdle = KEEPALIVE_IDLE;
    int keepInterval = KEEPALIVE_INTERVAL;
    int keepCount = KEEPALIVE_COUNT;
    struct sockaddr_storage dest_addr;

     if (addr_family == AF_INET) {
        struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
        dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
        dest_addr_ip4->sin_family = AF_INET;
        dest_addr_ip4->sin_port = htons(PORT);
        ip_protocol = IPPROTO_IP;
    }
    int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
    if (listen_sock < 0) {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        vTaskDelete(NULL);
        return;
    }
    int opt = 1;
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    ESP_LOGI(TAG, "Socket created");
    int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if (err != 0) {
        ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
        ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
        goto CLEAN_UP;
    }
    ESP_LOGI(TAG, "Socket bound, port %d", PORT);

    err = listen(listen_sock, 1);
    if (err != 0) {
        ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
        goto CLEAN_UP;
    }



    

    while (1) {
        struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
        socklen_t addr_len = sizeof(source_addr);
        int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
            break;
        }

        // Set tcp keepalive option
        setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
        if (source_addr.ss_family == PF_INET) {
            inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
        }

        ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);
        //循环处理数据
        //do_retransmit(sock);
        processSocket(sock);

        shutdown(sock, 0);
        close(sock);
        printf("close current socket\r\n");
    }

CLEAN_UP:
    close(listen_sock);
    vTaskDelete(NULL);
}